package com.ruoyi.dev.minio.controller;

import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.dev.minio.config.ConstansDevConfig;
import com.ruoyi.dev.minio.config.MinioConfig;
import com.ruoyi.dev.minio.domain.MinioFile;
import com.ruoyi.dev.minio.domain.file.FileChunk;
import com.ruoyi.dev.minio.dto.MinioResponseDTO;
import com.ruoyi.dev.minio.service.MinioFileService;
import com.ruoyi.dev.minio.service.impl.FileHadleServiceImpl;
import com.ruoyi.dev.minio.util.MinioClientUtils;

import io.minio.GetObjectArgs;
import io.minio.MinioClient;

import io.minio.RemoveObjectArgs;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.AllArgsConstructor;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.math.NumberUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.PropertySource;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.commons.CommonsMultipartFile;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.RandomAccessFile;
import java.net.URLEncoder;
import java.nio.channels.Channels;
import java.nio.channels.FileChannel;
import java.nio.channels.WritableByteChannel;

import java.time.Instant;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;


@RestController
@RequestMapping("/fileHandle")
@Slf4j
@AllArgsConstructor
@Api(tags = "文件处理模块")
public class FileHandleController {

//    private static String minioConfig.getDowloadPath() = "D://uploader";
    
    private MinioClientUtils minioClientUtils;

    private MinioConfig minioConfig;

    public MinioFileService minioFileService;

    @Autowired
    private MinioClient minioClient;

    @Autowired
    FileHadleServiceImpl fileHadleServiceImpl;


    @ApiOperation(value = "图片列表")
    @GetMapping("/minioList")
    public AjaxResult minioList() {
        
        List<MinioFile> list = minioFileService.selectList();

        return AjaxResult.success(list);
    }

    @ApiOperation(value = "列出存储桶中的所有对象名称")
    @GetMapping("/listObject")
    public AjaxResult listObject() {

        List<String> list = new ArrayList<>();

        try {
            String bucketName = minioConfig.getBucketName();
            list = minioClientUtils.listObjectNames(bucketName);

        } catch (Exception e) {
            log.error("获取文件出错:{}", e);
            return AjaxResult.error("获取文件出错");
        }

        return AjaxResult.success(list);
    }

    /**
     * 文件删除
     *
     * @param objectName 文件名 "2024-2-27/1709016248483366.png"
     * @throws IOException
     */
    @ApiOperation(value = "删除存储桶中单个对象名称")
    @GetMapping("/removeObject")
    public AjaxResult removeObject(String objectName) {

        // 只删除数据库图片地址 不删除 minIo文件
        try {
            String bucketName = minioConfig.getBucketName();
            // "/2024-2-27/1709013209632363.png"
            boolean flag = minioClientUtils.removeObject(bucketName, objectName);

            if (flag) {
                return AjaxResult.success();
            }
        } catch (Exception e) {
            log.error("获取文件出错:{}", e);
            return AjaxResult.error("获取文件出错");
        }

        return AjaxResult.error();
    }

    /**
     * 文件下载
     *
     * @param fileName 文件名 "2024-2-27/1709016248483366.png"
     * @param delete   是否删除
     * @throws IOException
     */
    @GetMapping("/fileDownload")
    @ResponseBody
    public void fileDownload(@RequestParam(name = "fileName") String fileName,
                             @RequestParam(defaultValue = "false") Boolean delete,
                             HttpServletResponse response) {
        String bucketName = minioConfig.getBucketName();
        InputStream inputStream = null;
        OutputStream outputStream = null;
        try {
            if (StringUtils.isBlank(fileName)) {
                response.setHeader("Content-type", "text/html;charset=UTF-8");
                String data = "文件下载失败";
                OutputStream ps = response.getOutputStream();
                ps.write(data.getBytes("UTF-8"));
                return;
            }

            outputStream = response.getOutputStream();
            // 获取文件对象
            inputStream = minioClient.getObject(GetObjectArgs.builder().bucket(bucketName).object(fileName).build());
            byte buf[] = new byte[1024];
            int length = 0;
            response.reset();
            response.setHeader("Content-Disposition", "attachment;filename=" +
                    URLEncoder.encode(fileName.substring(fileName.lastIndexOf("/") + 1), "UTF-8"));
            response.setContentType("application/octet-stream");
            response.setCharacterEncoding("UTF-8");
            // 输出文件
            while ((length = inputStream.read(buf)) > 0) {
                outputStream.write(buf, 0, length);
            }
            inputStream.close();
            // 判断：下载后是否同时删除minio上的存储文件
            if (BooleanUtils.isTrue(delete)) {
                minioClient.removeObject(RemoveObjectArgs.builder().bucket(bucketName).object(fileName).build());
            }
        } catch (Throwable ex) {
            response.setHeader("Content-type", "text/html;charset=UTF-8");
            response.setStatus(500);
            String data = "文件下载失败";
            try {
                OutputStream ps = response.getOutputStream();
                ps.write(data.getBytes("UTF-8"));
            } catch (IOException e) {
                e.printStackTrace();
            }
        } finally {
            try {
                outputStream.close();
                if (inputStream != null) {
                    inputStream.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }


    @ApiOperation(value = "上传文件,支持批量上传")
    @PostMapping("/uploadFile")
    public AjaxResult uploadFile(@RequestParam(value = "files", required = true) MultipartFile[] files) {

        List<MinioResponseDTO> minioResponseDTOList = new ArrayList<>();
        for (MultipartFile file : files) {
            String originalFilename = file.getOriginalFilename();

            String extName = originalFilename.substring(originalFilename.lastIndexOf(".") + 1);
            log.info("文件拓展名:" + extName);
            //生成新的文件名，存入到minio
            long millSeconds = Instant.now().toEpochMilli();
            String minioFileName = LocalDate.now().getYear() + "-" + LocalDate.now().getMonthValue() + "-" + LocalDate.now().getDayOfMonth()
                    + "/" + millSeconds + RandomStringUtils.randomNumeric(3) + "." + extName;

            String contentType = file.getContentType();
            log.info("文件mime:{}", contentType);
            //返回文件大小,单位字节
            long size = file.getSize();
            log.info("文件大小：" + size);

            try {
                String bucketName = minioConfig.getBucketName();
                minioClientUtils.putObject(bucketName, file, minioFileName);
                String fileUrl = minioClientUtils.getObjectUrl(bucketName, minioFileName);

                MinioFile minioFile = new MinioFile();
                minioFile.setOriginalFileName(originalFilename);
                minioFile.setFileExtName(extName);
                minioFile.setFileName(minioFileName);
                minioFile.setFileSize(size);
                minioFile.setMime(contentType);
                minioFile.setIsDelete(NumberUtils.INTEGER_ONE);
                minioFile.setFileUrl(fileUrl);

                boolean insert = minioFileService.insert(minioFile);

                if (insert) {
                    MinioResponseDTO minioResponseDTO = new MinioResponseDTO();
                    minioResponseDTO.setFileId(minioFile.getId());
                    minioResponseDTO.setOriginalFileName(originalFilename);
                    minioResponseDTO.setFileUrl(fileUrl);
                    minioResponseDTOList.add(minioResponseDTO);
                }

            } catch (Exception e) {
                log.error("上传文件出错:{}", e);
                return AjaxResult.error("上传文件出错");
            }
        }

        return AjaxResult.success(minioResponseDTOList);
    }


    /**
     * 仅仅用于测试，是否可以正常上传文件
     *
     * @return
     * @throws Exception
     */
    @GetMapping("/test")
    @ApiOperation(value = "测试minio文件上传")
    public AjaxResult testPutObject() throws Exception {
        FileInputStream fileInputStream = new FileInputStream("C:\\Users\\MSI\\Desktop\\新建文本文档.txt");
        boolean bs = minioClientUtils.putObject("fsp-dev", "新建文本文档.txt", fileInputStream, "image/jpg");
        log.info("上传成功?" + bs);
        return AjaxResult.success("上传成功");
    }


    /**
     * 断点下载文件
     *
     * @return
     */
    @GetMapping("appDownloadAndroid")
    public void download(HttpServletRequest request, HttpServletResponse response) throws IOException {

        //地址
        String path = "";
        File file = new File(path);
        if (!file.exists()) {
            response.setStatus(HttpStatus.NOT_FOUND.value());
            return;
        }
        long fromPos = 0;
        long downloadSize = file.length();
        //getHeader这个方法取请求头，是忽略大小写的！
        if (request.getHeader("Range") != null) {
            //返回206
            response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
            String[] ary = request.getHeader("Range").replaceAll("bytes=", "").split("-");
            fromPos = Long.parseLong(ary[0]);
            downloadSize = (ary.length < 2 ? downloadSize : Long.parseLong(ary[1])) - fromPos;
        }
        //注意下面设置的相关请求头
        response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE);
        //相当于设置请求头content-length
        response.setContentLengthLong(downloadSize);

        //使用URLEncoder处理中文名(否则会出现乱码)
        response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(file.getName(), "UTF-8"));
        response.setHeader("Accept-Ranges", "bytes");
        response.setHeader("Content-Range", String.format("bytes %s-%s/%s", fromPos, (fromPos + downloadSize), file.length()));

        RandomAccessFile randomAccessFile = new RandomAccessFile(file, "rw");
        randomAccessFile.seek(fromPos);

        FileChannel inChannel = randomAccessFile.getChannel();
        WritableByteChannel outChannel = Channels.newChannel(response.getOutputStream());

        try {
            while (downloadSize > 0) {
                long count = inChannel.transferTo(fromPos, downloadSize, outChannel);
                if (count > 0) {
                    fromPos += count;
                    downloadSize -= count;
                }
            }
            inChannel.close();
            outChannel.close();
            randomAccessFile.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }


    public static void main(String[] args) {

//        getMultipartFile(new File("D:\\uploader\\real\\431960.zip"));
    }

    //将File转换为MultipartFile
    public static MultipartFile getMultipartFile(File file) {
        FileItem item = new DiskFileItemFactory().createItem("file"
                , MediaType.MULTIPART_FORM_DATA_VALUE
                , true
                , file.getName());
        try (InputStream input = new FileInputStream(file);
             OutputStream os = item.getOutputStream()) {
            // 流转移
            IOUtils.copy(input, os);
        } catch (Exception e) {
            throw new IllegalArgumentException("Invalid file: " + e, e);
        }
        log.info("1111");

        return new CommonsMultipartFile(item);
    }


    /**
     * Get方法：校验分片信息
     *
     * @param fileChunk 分片信息
     * @return AjaxResult
     */
    @GetMapping("/upload")
    public AjaxResult checkChunk(FileChunk fileChunk) {
        log.info("校验分片——开始:{}", fileChunk.toString());
        //查询分片是否上传
        File checkChunk = new File(minioConfig.getDowloadPath() + File.separator + "temp" + File.separator + fileChunk.getIdentifier() + File.separator + fileChunk.getChunkNumber());
        Map<String, Object> map = new HashMap<>();
        //如果不存在返回
        if (!checkChunk.exists()) {
            map.put("uploaded", false);
            return AjaxResult.success(map);
        } else {
            //如果存在，查询当前文件已经上传了哪些分片了
            File file = new File(minioConfig.getDowloadPath() + File.separator + "temp" + File.separator + fileChunk.getIdentifier());
            final File[] files = file.listFiles();
            List<Integer> uploadedChunksList = new ArrayList<>();
            if (files != null && files.length > 0) {
                for (File chunk : files) {
                    //如果分片大小相等，且分片不等于最后一片，返回已经上传的分片列表
                    if (chunk.length() == minioConfig.getFileSize() && (Integer.parseInt(chunk.getName()) != fileChunk.getTotalChunks())) {
                        uploadedChunksList.add(Integer.parseInt(chunk.getName()));
                    }
                }
            }
            Integer[] uploadedChunks = new Integer[0];
            if (!uploadedChunksList.isEmpty()) {
                uploadedChunks = uploadedChunksList.toArray(new Integer[uploadedChunksList.size()]);
            }
            map.put("uploadedChunks", uploadedChunks);
            log.info("校验分片——结束:{}", fileChunk.toString());
            return new AjaxResult(com.ruoyi.common.constant.HttpStatus.SUCCESS, "上传成功", map);
        }
    }

    /**
     * Post方法：分片上传
     *
     * @param fileChunk 分片
     * @return AjaxResult
     */
    @PostMapping("/upload")
    public AjaxResult uploadChunk(FileChunk fileChunk) {
        log.info("上传分片——开始:{}", fileChunk.toString());
        if (fileChunk.getFile().isEmpty()) {
            log.error("上传文件不存在！");
            throw new RuntimeException("上传文件不存在！");
        }
        File chunkPath;
        //判断是否是文件夹
//        if (fileChunk.getRelativePath().contains("/")) {
//            final String parentPath = fileChunk.getRelativePath().substring(0, fileChunk.getRelativePath().lastIndexOf("/"));
//            chunkPath = new File(minioConfig.getDowloadPath() + File.separator + "temp" + File.separator + parentPath + File.separator + fileChunk.getIdentifier());
//        } else {
//            chunkPath = new File(minioConfig.getDowloadPath() + File.separator + "temp" + File.separator + fileChunk.getIdentifier());
//        }
        chunkPath = new File(minioConfig.getDowloadPath() + File.separator + "temp" + File.separator + fileChunk.getIdentifier());
        if (!chunkPath.exists()) {
            final boolean flag = chunkPath.mkdirs();
            if (!flag) {
                log.error("创建目录失败！");
                return new AjaxResult(com.ruoyi.common.constant.HttpStatus.NOT_MODIFIED, "上传失败");
            }
        }
        RandomAccessFile raFile = null;
        BufferedInputStream inputStream = null;
        try {
            File chuckFile = new File(chunkPath, String.valueOf(fileChunk.getChunkNumber()));
            raFile = new RandomAccessFile(chuckFile, "rw");
            raFile.seek(raFile.length());
            inputStream = new BufferedInputStream(fileChunk.getFile().getInputStream());
            byte[] buf = new byte[1024];
            int length = 0;
            while ((length = inputStream.read(buf)) != -1) {
                raFile.write(buf, 0, length);
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        } finally {
            if (inputStream != null) {
                try {
                    inputStream.close();
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }
            if (raFile != null) {
                try {
                    raFile.close();
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }
        }
        log.info("上传分片——结束:{}", fileChunk.toString());
        return new AjaxResult(com.ruoyi.common.constant.HttpStatus.SUCCESS, "上传成功");
    }

    /**
     * 合并文件
     *
     * @param fileChunk 分片信息
     * @return AjaxResult
     */
    @PostMapping("/merge")
    public AjaxResult merge(@RequestBody FileChunk fileChunk) {
        log.info("合并文件——开始：{}", fileChunk.toString());
        //分片文件临时目录
        File tempPath = new File(minioConfig.getDowloadPath() + File.separator + "temp" + File.separator + fileChunk.getIdentifier());
        //真实上传路径
        File realPath = new File(minioConfig.getDowloadPath() + File.separator + "real");
        if (!realPath.exists()) {
            realPath.mkdirs();
        }
        File realFile = new File(minioConfig.getDowloadPath() + File.separator + "real" + File.separator + fileChunk.getFilename());
        //判断目标文件是否存在，若存在删除
//        if (realFile.exists()) {
//            realFile.delete();
//        } else {
//            try {
//                realFile.createNewFile();
//            } catch (IOException e) {
//                throw new RuntimeException(e);
//            }
//        }
        // 文件追加写入
        FileOutputStream os;
        try {

            os = new FileOutputStream(realFile, true);
            if (tempPath.exists()) {
                //获取临时目录下的所有文件
                File[] tempFiles = tempPath.listFiles();
                //按名称排序
                Arrays.sort(tempFiles, (o1, o2) -> {
                    if (Integer.parseInt(o1.getName()) < Integer.parseInt(o2.getName())) {
                        return -1;
                    }
                    if (Integer.parseInt(o1.getName()) == Integer.parseInt(o2.getName())) {
                        return 0;
                    }
                    return 1;
                });
                //每次读取10MB大小，字节读取
                byte[] bytes = new byte[10 * 1024 * 1024];
                int len;

                for (int i = 0; i < tempFiles.length; i++) {
                    FileInputStream fis = new FileInputStream(tempFiles[i]);
                    while ((len = fis.read(bytes)) != -1) {
                        os.write(bytes, 0, len);
                    }
                    fis.close();
                    //删除分片
                    tempFiles[i].delete();
                }


                os.close();
                //删除临时目录
                if (tempPath.isDirectory() && tempPath.exists()) {
                    System.gc(); // 回收资源
                    tempPath.delete();
                }

                //文件转换 并上传至minio
//                fileHadleServiceImpl.fileToMultipartFile(realFile);
                fileHadleServiceImpl.onCallback(realFile);
            }
        } catch (Exception e) {
            log.error("文件合并——失败 " + e.getMessage());
            return AjaxResult.error("文件合并失败：" + e.getMessage());
        }
        log.info("合并文件——结束：{}", fileChunk.toString());
        return AjaxResult.success("合并成功");
    }
}
